Apple, the Apple logo, and Macintosh are registered trademarks of Apple Computer, Inc.
Mac and OpenDoc are trademarks of Apple Computer, Inc.
Foci and the Arbitrator
OpenDoc has a flexible and platform-neutral model of part activation, in which part editors request ownership of named foci from an arbitrator. Each session has an arbitrator, which can be obtained from the session using ODSession::GetArbitrator(). The foci are defined as ISO strings (in Foci.idl and its bindings Foci.xh and Foci.h), and the standard set includes:
These strings can be tokenized using ODSession::Tokenize(). The methods of ODArbitrator and ODFocusSet use the tokenized form of the focus names.
Foci are owned by frames. The active border is drawn around the frame with the selection focus, and Shift-clicks and Command-clicks are sent to this frame. Menu and keystroke events are sent to the frames with the menu focus and keystroke focus, respectively. Page Up and Page Down keystrokes are sent to the frame with the scrolling focus. See the Dialogs recipe for information about the modal focus.
There is no assumption that the same frame owns the selection, key and menu foci, even though this will often be the case. Furthermore, OpenDoc does not require that the selection focus be in an active window, even though this is usually what’s desired on Macintosh, unless the active window is a modeless dialog. See Window Activation and the Selection Focus, below.
A part can request (for one of its frames) ownership of a single focus, or a set of foci (an ODFocusSet) using the ODArbitrator methods RequestFocus and RequestFocusSet(). RequestFocusSet() performs a “two-phase commit”. It first asks each current owner if it is willing to relinquish the focus (by calling Part::BeginRelinquishFocus()). If any focus owner is unwilling, the arbitrator aborts the request by calling each part’s AbortRelinquishFocus() method, and RequestFocusSet() returns kODFalse. If all focus owners are cooperative, the arbitrator calls each one’s CommitRelinquishFocus() method, and RequestFocusSet() returns kODTrue.
Note: OpenDoc does not activate parts. Parts activate themselves by requesting foci for frames.
Transferring a Focus or Focus Set
A focus or set of foci can also be forcibly transferred from one frame to another, without negotiation. ODArbitrator::TransferFocus() and TransferFocusSet() are used for this purpose. When a focus is transferred, the FocusAcquired() method of the new owner's part is called, and the FocusLost method of the old owner's part is called, unless it is the one doing the transferring (the transferring frame is passed to TransferFocus() and TransferFocusSet()).
Parts should generally request foci rather than transfer them. However, the recipe for handling modal dialogs makes use of TransferFocus. Since modal dialogs might be nested, the recipe consists of saving the current owner of the modal focus, requesting the modal focus, and then transferring it back to the saved owner when the dialog is dismissed.
Note: Actually, if the nested modal dialogs do not themselves contain parts, the above is not strictly necessary, and the part displaying the modal dialog can simply relinquish the modal focus.
While parts generally don't transfer foci, they should be prepared to have certain foci forcibly removed. In this case the FocusLost() method will be called.
Note: FocusAcquired() and FocusLost() are not called during the Request process. The requesting part knows it has acquired the foci because RequestFocus or RequestFocusSet returns kODTrue. The requesting part may be tempted to call its own FocusAcquired() method when RequestFocus() or RequestFocusSet() returns kODTrue, but it's better to use separate private methods which can be called from both FocusAcquired() and when a request returns kODTrue. This way there is a clear separation between the external and internal calls.
Similarly, a part may be tempted to call its own FocusLost() method from its CommitRelinquishFocus() method, but it would be better to keep the external and internal interfaces separate.
Keyboard Navigation
A containing part (such as a forms package) could provide keyboard navigation of embedded parts by transferring the keyboard and/or selection focus from one embedded part to another. The containing part would also have to set the doesPropagateEvents flag of embedded frames, so that it could handle Tab or arrow keys not consumed by the embedded parts. This recipe needs further research, but the containing part should probably request the focus for its own frame (so that an active embedded part can refuse to relinquish it if appropriate), and then transfer the focus to the next embedded frame.
The Scrolling Focus
PageDown and PageUp keys are sent to the part with the scrolling focus. If an embedded part with no scroll bars is active, the user should still be able to page through the document. Typically, containers will have scroll bars, and embedded parts wont, although other configurations are possible. The following rules should be followed:
An embedded part with the scolling focus should always relinquish it when asked to.
A root part in an active window should not relinquish the scrolling focus.
Basic Frame Activation
Foci and Focus Sets
Most frames will require a focus set containing the selection focus, keystroke focus and menu focus when the user clicks in the frame. But a frame with scroll bars might also need the scrolling focus. And a frame for a modeless dialog may require the menu focus, but not the selection focus, so that the active border remains around the frame which opened the dialog, even though it's in an inactive window.
So it's best to preserve focus state on a per-frame basis, and this is one reason why those frame objects attached to the part info of ODFrame objects are so handy.
It's a good idea for each frame obect to pre-allocate an ODFocusSet when it is initialized. The ODArbitrator method CreateFocusSet should be used to create the set.
Note: In this example, the tokenized focus names are also cached in each frame. They could be cached in the part object, or even in a reference-counted C++ object stored in a library global shared by all part instances in a document. Per-context shared-library globals makes this practical. See the Shared Utility Windows recipe.
Frames are activated in a variety of circumstances - on mouse down, on mouse up, when a window is first opened, when a window is activated.
MouseUp Activation: When a frame is already selected, a mouse-down event in that frame will go to the containing frame so that it can allow the user to drag the selected frame. The embedded frame will receive a mouse-up event if the container decides a drag is not happening. Therefore frames should activate themselves if necessary on mouse-up. For reasons explained in the Mouse Events recipe, they still need to activate on mouse down as well.
When the part editor receives an event, and decides it must activate the frame, it requests the set of foci needed by the frame:
A part will usually relinquish a focus when another part asks for it via BeginRelinquishFocus(). In this case, the part simply returns kODTrue in BeginRelinquishFocus(), and removes menus, palettes, blinking cursors and so forth in CommitRelinquishFocus().
Most parts will willingly relinquish the common foci when asked, with the exception of the modal focus. The code below is somewhat simplified. The part may in fact wish to relinquish the modal focus to another of its own frames in cases where a nested modal dialog is being displayed from within the frame that owns the modal focus.
If a part does anything more than return kODTrue or kODFalse in BeginRelinquishFocus(), it will have to undo the effects in AbortRelinquishFocus().
A part must also relinquish foci when a frame is going away. In the methods delegated to by Part::DisplayFrameClosed() and Part:: DisplayFrameRemoved(), or when the last facet is removed:
A part may also wish to ensure that it keeps various foci together, so it may wish to relinquish a set if it is notified via FocusLost() that it lost a particular focus.
Window Activation and Frame Activation
Not all platforms have a notion of active windows, but for those that do, following these recipes results in sensible behavior in several cases:
A frame with the selection focus can record this fact when its window is deactivated, and preserve a background selection. When the window is reactivated (eg. by clicking on its title bar), the formerly active frame regains the selection focus. When the user clicks in the content area of an inactive window, the part which receives the event will activate the window, unless the click is in a background selection, in which case the part might allow the selection to be dragged without activating the window. In this case, the destination window will be activated when the drop is completed.
When a new document is opened from stationery, the menus of the root part will appear. The same will be true when the user opens an existing document. It is not necessarily recommended, but an embedded part can choose to record its active state when saved and attempt to restore it when the document is opened. And the part at the root can choose to allow this, or always ensure that it is active itself.
As shown in the following recipes, the behavior described above is achieved by saving the state of a frame when a window deactivate event is received, and restoring it when the window is activated. The frame object attached to the part info makes use of two Boolean properties fNeedsFoci and fHasFoci.
The basic recipe is fairly simple. The frame handler object is attached to the part info in Part::DisplayFrameAdded() and and Part::DisplayFrameConnected(). The fNeedsFoci flag is set to true if the frame is at the root. The flag is also set to true or false when a window is deactivated, depending on whether or not the frame is active. The fNeedsFoci flag is checked when the part receives a window activate event, and if it is true, the frame is activated.
Creating a New Window
The first time a window, and hence a root frame, is created, the part needs to ensure that the root frame is active when the window becomes active. The frame handler object will be created and attached in Part::DisplayFrameAdded():
When a window is saved in a document, and the document is reopened, the part's DisplayFrameConnected() method is called, and the frame handler object will be attached there.
When a window is activated or deactivated
When a window is first created or is brought to the front, each part will receive an activate event for each facet it has in that window. If the user clicks in the title bar of an inactive window, the window will be brought to the front and each part in the previously active window will get a deactivate event for each facet it has in that window. If a frame has foci, it sets “fNeedsFoci” to true, so that the next time that window is activated, the part can reclaim the foci.
When a different frame in the same window is activated
When the user clicks in a different frame in the same window, one part editor will request foci and install menus and other interface elements. As described earlier, this kind of activation is done in several places (mouse down, mouse up, window activate) , so it's a good idea to record that a frame has the selection focus, so that the method which activates a frame can exit quickly if the frame is already active. After RequestFocusSet() succeeds (eg. when handling mouse down and mouse up), fNeedsFoci and fHasFoci are updated:
If a part does wish to save its selection persistently even when it is embedded, it can do the following.
Note : The Human Interface guidelines do not recommend this. Note also that the root part must cooperate by not activating itself if a part already has the selection focus, as shown below.
This works because activate events are sent bottom-up to the facets in the window. Note also that the part externalizing the flag must mark the draft as changed when it acquires the selection focus, so that Save is enabled.